﻿//    Copyright 2010,2011 Himanshu Vasishth
//   
//    This file is part of SecretVault.
//
//    SecretVault is free software: you can redistribute it and/or modify
//    it under the terms of the GNU General Public License as published by
//    the Free Software Foundation, either version 3 of the License, or
//    (at your option) any later version.
//
//    SecretVault is distributed in the hope that it will be useful,
//    but WITHOUT ANY WARRANTY; without even the implied warranty of
//    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//    GNU General Public License for more details.
//
//    You should have received a copy of the GNU General Public License
//    along with SecretVault.  If not, see <http://www.gnu.org/licenses/>.
//
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Security.Cryptography;
using System.IO;
using Common.Exceptions;
using PasswordStoreProvider;
using PasswordStoreProvider.Exceptions;
using System.Windows.Forms;
using System.Drawing;
using PasswordLocker.Properties;
using Common;
using PasswordLocker.UI;
using PasswordLocker.Controls;
using System.Diagnostics;

namespace PasswordLocker
{
    /// <summary>
    /// Manages the secrets
    /// </summary>
    public class PasswordManager
    {
        private IVaultStore passwordStore;
        private SecretVaultForm secretVaultForm;
        public PasswordManager()
        {
        }

        #region Public methods

        /// <summary>
        /// Starts the application. Does the following
        /// - Get path of existing locker file.
        /// -   If path not found, ask user to create a new one.
        /// -   If path found, ask user to enter the password to open the store, or to create a new one.
        /// - Get the password, and open the store.
        /// - If store opens, get the loginInfo list, set it on the View.
        /// - View callbacks will call into handlers into this class, which will then interact with the store
        ///   to return the data back.
        /// </summary>
        public void StartApplication()
        {
            // Open the store
            passwordStore = DoOpenStore();

            if(passwordStore == null)
            {
                // return if user cancelled the open/create operation
                return;
            }

            // Save the location for the store as default
            Settings.Default.DBFileLocation = passwordStore.StoreLocation;
            Settings.Default.Save();

            // Show the UI with the list of LoginInfoObjects
            secretVaultForm = new SecretVaultForm();
            secretVaultForm.StoreLocation = passwordStore.StoreLocation;
            secretVaultForm.AddClicked += AddLoginInfoClicked;
            secretVaultForm.ChangeLoginInfoClicked += ChangeLoginInfoClicked;
            secretVaultForm.DeleteLoginInfoClicked += DeleteLoginInfoClicked;
            secretVaultForm.ShowPasswordClicked += ShowPasswordClicked;
            secretVaultForm.ChangeMasterPassword += ChangeMasterPassword;

            UpdateUI();

            Application.Run(secretVaultForm);
        }

        void ChangeMasterPassword(object sender, EventArgs e)
        {
            GetMasterPasswordForm.GetMasterPasswordAndRunAction(secretVaultForm,
                        masterPassword =>
                            {
                                // TODO: Create a single dialog to handle this
                                var newMasterPassword = new NewMasterPasswordForm();
                                string newPassword = string.Empty;
                                newMasterPassword.OkClicked +=
                                    (sender2, e2) =>
                                        {
                                            newPassword = newMasterPassword.Password;
                                            newMasterPassword.Close();
                                        };
                                newMasterPassword.CancelClicked +=
                                    (sender3, e3) => newMasterPassword.Close();
                                newMasterPassword.ShowDialog(secretVaultForm);

                                if (!string.IsNullOrEmpty(newPassword))
                                {
                                    passwordStore.ChangeMasterPassword(
                                        masterPassword, newPassword);
                                }

                            });
        }

        #endregion

        #region Private methods

        private void UpdateUI()
        {
            secretVaultForm.LoginInfos = passwordStore.LoginInfos();
        }

        private void ShowPasswordClicked(LoginInfo loginInfo)
        {
            Debug.Assert(loginInfo != null);

            GetMasterPasswordForm.GetMasterPasswordAndRunAction(secretVaultForm,
                    masterPassword =>
                        secretVaultForm.ShowPassword(
                            loginInfo,
                            passwordStore.GetPassword(masterPassword, loginInfo))
                        );
        }

        private void DeleteLoginInfoClicked(LoginInfo loginInfo)
        {
            GetMasterPasswordForm.GetMasterPasswordAndRunAction(secretVaultForm,
                    masterPassword =>
                        passwordStore.DeleteLoginInfo(masterPassword, loginInfo));
            UpdateUI();
        }

        private void ChangeLoginInfoClicked(LoginInfo loginInfo)
        {
            var loginInfoForm = new LoginInfoForm();
            loginInfoForm.OKClicked += ChangeLoginInfoForm_OKClicked;
            loginInfoForm.LoginInfo = loginInfo;
            loginInfoForm.Tag = loginInfo;

            if (loginInfoForm.ShowDialog(secretVaultForm) == DialogResult.OK)
            {
                // update the list of resources displayed
                UpdateUI();
            }
        }

        /// <summary>
        /// Invoked when OK button on the form used to update login info is clicked
        /// </summary>
        void ChangeLoginInfoForm_OKClicked(LoginInfoForm loginInfoForm)
        {
            GetMasterPasswordForm.GetMasterPasswordAndRunAction(secretVaultForm,
                masterPassword =>
                {
                    passwordStore.UpdateLoginInfo(
                        masterPassword,
                        loginInfoForm.Tag as LoginInfo,
                        loginInfoForm.LoginInfo,
                        loginInfoForm.Password);

                    // The dialog is closed in the event handler to ensure that if user
                    // types the incorrect master password they go back to LoginInfoForm
                    // 
                    loginInfoForm.Close();
                    loginInfoForm.DialogResult = DialogResult.OK;
                });
        }

        private void AddLoginInfoClicked(object sender, EventArgs e)
        {
            var loginInfoForm = new LoginInfoForm();
            loginInfoForm.OKClicked += AddLoginInfo_OKClicked;
            if (loginInfoForm.ShowDialog(secretVaultForm) == DialogResult.OK)
            {
                // update the list of resources displayed
                UpdateUI();
            }
        }

        /// <summary>
        /// Called when OK is clicked on the dialog used to add a new login info
        /// </summary>
        /// <param name="loginInfoForm"></param>
        private void AddLoginInfo_OKClicked(LoginInfoForm loginInfoForm)
        {
            GetMasterPasswordForm.GetMasterPasswordAndRunAction(secretVaultForm,
                masterPassword =>
                {
                    passwordStore.StoreLoginInfo(
                        masterPassword,
                        loginInfoForm.LoginInfo,
                        loginInfoForm.Password);
                    // The dialog is closed in the event handler to ensure that if user
                    // types the incorrect master password they go back to LoginInfoForm
                    // 
                    loginInfoForm.Close();
                    loginInfoForm.DialogResult = DialogResult.OK;
                });
        }

        /// <summary>
        /// Displays the UI required to open the store, opens the store and returns it.
        /// Will return null if user cancels the open operation.
        /// </summary>
        private IVaultStore DoOpenStore()
        {
            IVaultStore store = null;

            // Do the Open Dialog
            var openStoreForm = new OpenStoreForm();
            openStoreForm.StoreLocation = Settings.Default.DBFileLocation;
            openStoreForm.OpenStore += 
                (sender, e) =>
                    {
                        try
                        {
                            // Create a store and try an open it
                            store = VaultStoreFactory.OpenStore(openStoreForm.MasterPassword,
                                                                openStoreForm.StoreLocation);
                            // close the open form
                            openStoreForm.Close();
                        }
                        catch (Exception)
                        {
                            // TODO: Handle specific exceptions
                            // Catch exceptions and show a message box telling the user password is wrong
                            MessageBox.Show(openStoreForm, 
                                            Resources.InvalidMassterPasswordError, 
                                            Resources.AppName,
                                            MessageBoxButtons.OK, 
                                            MessageBoxIcon.Error);
                        }
                    };
            openStoreForm.CreateNew +=
                (sender, e) =>
                    {
                        // close the openForm and do the CreateNewStore workflow
                        store = DoCreateNewStore(openStoreForm);
                        if (store != null)
                        {
                            // don't close the open existing store dialog if 
                            // user canceled the create new operation for some reason
                            openStoreForm.Close();
                        }
                    };

            // Show the open dialog
            openStoreForm.ShowDialog();

            return store;
        }

        /// <summary>
        /// Does the create new store workflow and returns an IVaultStore for the new store
        /// </summary>
        /// <returns>
        /// IVaultStore object representing the created store. Null if the create operation was cancelled.
        /// </returns>
        private static IVaultStore DoCreateNewStore(IWin32Window ownerWindow)
        {
            IVaultStore store = null;

            var storeLocation = string.Empty;
            var createNewVaultForm = new CreateNewVaultForm();
            createNewVaultForm.CancelClicked += (sender, e) => createNewVaultForm.Close();
            createNewVaultForm.NextClicked += (sender, e) =>
                                                  {
                                                      storeLocation = createNewVaultForm.FileLocation;
                                                      createNewVaultForm.Close();
                                                  };
            createNewVaultForm.ShowDialog(ownerWindow);

            if(string.IsNullOrEmpty(storeLocation))
            {
                return null;
            }

            var newMasterPasswordForm = new NewMasterPasswordForm();
            newMasterPasswordForm.CancelClicked += 
                (sender, e) => newMasterPasswordForm.Close();
            newMasterPasswordForm.OkClicked +=
                (sender, e) =>
                    {
                        try
                        {
                            store = VaultStoreFactory.OpenStore(newMasterPasswordForm.Password,
                                                                storeLocation);
                            newMasterPasswordForm.Close();
                        }
                        catch (Exception)
                        {
                            // We really shouldn't get an error here since user opted to create
                            // a new store! Tell the user an unexpected error happen while trying
                            // to create the store
                            MessageBox.Show(newMasterPasswordForm,
                                            Resources.UnexpectedError,
                                            Resources.AppName, 
                                            MessageBoxButtons.OK, 
                                            MessageBoxIcon.Error);
                        }
                    };
            newMasterPasswordForm.ShowDialog(ownerWindow);

            return store;
        }

        #endregion
    }
}
